{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sdm as sdmlib\n",
    "from collections import defaultdict\n",
    "import random\n",
    "from IPython.display import clear_output\n",
    "\n",
    "empty = ' '"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Player(object):\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "    \n",
    "    def on_invalid_move(self):\n",
    "        raise Exception('Ops')\n",
    "    \n",
    "    def on_finish(self, winner, seq):\n",
    "        pass\n",
    "    \n",
    "    def next_move(self, step, board):\n",
    "        v = []\n",
    "        for i, row in enumerate(board):\n",
    "            for j, x in enumerate(row):\n",
    "                if x == empty:\n",
    "                    v.append((i, j))\n",
    "        return random.choice(v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SDMPlayer(object):\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "\n",
    "        self.bits = 256\n",
    "        self.sample = 1000000\n",
    "        self.scanner_type = sdmlib.SDM_SCANNER_THREAD\n",
    "        \n",
    "        self.address_space = sdmlib.AddressSpace.init_random(self.bits, self.sample)\n",
    "        self.counter = sdmlib.Counter.init_zero(self.bits, self.sample)\n",
    "        self.sdm = sdmlib.SDM(self.address_space, self.counter, 103, self.scanner_type)\n",
    "        \n",
    "        self.bs_steps = [sdmlib.Bitstring.init_random(self.bits) for _ in range(10)]\n",
    "        self.code_bits = self.bits // 9\n",
    "        self.bs_codes = {\n",
    "            'X': sdmlib.Bitstring.init_random(self.code_bits),\n",
    "            'O': sdmlib.Bitstring.init_random(self.code_bits),\n",
    "            ' ': sdmlib.Bitstring.init_random(self.code_bits),\n",
    "        }\n",
    "        \n",
    "        self.reset_stats()\n",
    "    \n",
    "    def reset_stats(self):\n",
    "        self.stats = defaultdict(int)\n",
    "    \n",
    "    def on_invalid_move(self):\n",
    "        raise Exception('Ops')\n",
    "    \n",
    "    def board_to_bitstring(self, board):\n",
    "        bs = sdmlib.Bitstring.init_zeros(self.bits)\n",
    "        bit = 0\n",
    "        for row in board:\n",
    "            for x in row:\n",
    "                code = self.bs_codes[x]\n",
    "                for i in range(self.code_bits):\n",
    "                    bs.set_bit(bit, code.get_bit(i))\n",
    "                    bit += 1\n",
    "        return bs\n",
    "    \n",
    "    def bitstring_to_board(self, ref):\n",
    "        board = [[empty]*3 for _ in range(3)]\n",
    "        bit = 0\n",
    "        for i in range(3):\n",
    "            for j in range(3):\n",
    "                bs1 = sdmlib.Bitstring.init_zeros(self.code_bits)\n",
    "                for k in range(self.code_bits):\n",
    "                    bs1.set_bit(k, ref.get_bit(bit))\n",
    "                    bit += 1\n",
    "                found = False\n",
    "                for x, code in self.bs_codes.items():\n",
    "                    if code == bs1:\n",
    "                        board[i][j] = x\n",
    "                        found = True\n",
    "                if not found:\n",
    "                    #print 'Not found'\n",
    "                    return None\n",
    "        return board\n",
    "    \n",
    "    def on_finish(self, winner, seq):\n",
    "        debug = False\n",
    "        if winner == self.name:\n",
    "            prev = None\n",
    "            if debug:\n",
    "                print('Learning...')\n",
    "            for step, board in enumerate(seq):\n",
    "                if prev is not None:\n",
    "                    if debug:\n",
    "                        print_board(prev)\n",
    "                        print('')\n",
    "                        print_board(board)\n",
    "                        print('--')\n",
    "                    bs1 = self.board_to_bitstring(prev)\n",
    "                    bs2 = self.board_to_bitstring(board)\n",
    "                    self.sdm.write(bs1 ^ self.bs_steps[step], bs2)\n",
    "                prev = board\n",
    "    \n",
    "    def next_move(self, step, board):\n",
    "        bs1 = self.board_to_bitstring(board)\n",
    "        bs2 = self.sdm.iter_read(bs1 ^ self.bs_steps[step], max_iter=12)\n",
    "        board2 = self.bitstring_to_board(bs2)\n",
    "        if board2 is None:\n",
    "            return self.random_move(step, board)\n",
    "        diff = []\n",
    "        for i in range(3):\n",
    "            for j in range(3):\n",
    "                if board[i][j] != board2[i][j]:\n",
    "                    if board[i][j] == empty and board2[i][j] == self.name:\n",
    "                        diff.append((i, j))\n",
    "        if diff:\n",
    "            self.stats['sdm'] += 1\n",
    "            return random.choice(diff)\n",
    "        return self.random_move(step, board)\n",
    "        #print diff\n",
    "        #print ''\n",
    "        #print_board(board)\n",
    "        #print ''\n",
    "        #print_board(board2)\n",
    "        #print ''\n",
    "        #raise Exception\n",
    "    \n",
    "    def random_move(self, step, board):\n",
    "        self.stats['random'] += 1\n",
    "        v = []\n",
    "        for i, row in enumerate(board):\n",
    "            for j, x in enumerate(row):\n",
    "                if x == empty:\n",
    "                    v.append((i, j))\n",
    "        return random.choice(v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def print_board(board):\n",
    "    for row in board:\n",
    "        print(row)\n",
    "\n",
    "def check_all_equal(*args):\n",
    "    if len(set(args)) == 1 and args[0] != empty:\n",
    "        return True\n",
    "    return False\n",
    "        \n",
    "def check_for_winner(board):\n",
    "    for i in range(3):\n",
    "        if check_all_equal(board[i][0], board[i][1], board[i][2]):\n",
    "            return board[i][0]\n",
    "        \n",
    "        if check_all_equal(board[0][i], board[1][i], board[2][i]):\n",
    "            return board[0][i]\n",
    "\n",
    "    if check_all_equal(board[0][0], board[1][1], board[2][2]):\n",
    "        return board[0][0]\n",
    "    \n",
    "    if check_all_equal(board[0][2], board[1][1], board[2][0]):\n",
    "        return board[0][2]\n",
    "    \n",
    "    return None\n",
    "\n",
    "def copy_board(board):\n",
    "    v = []\n",
    "    for row in board:\n",
    "        v.append(list(row))\n",
    "    return v\n",
    "\n",
    "def play(p1, p2):\n",
    "    board = [[empty]*3 for _ in range(3)]\n",
    "    end = False\n",
    "    players = [p1, p2]\n",
    "    random.shuffle(players)\n",
    "    index = 0\n",
    "    step = 0\n",
    "    sequence = [board]\n",
    "    winner = None\n",
    "    while not end:\n",
    "        cur_player = players[index]\n",
    "        \n",
    "        i, j = cur_player.next_move(step, board)\n",
    "        if board[i][j] != empty:\n",
    "            cur_player.on_invalid_move()\n",
    "        board = copy_board(board)\n",
    "        board[i][j] = cur_player.name\n",
    "        sequence.append(board)\n",
    "        \n",
    "        winner = check_for_winner(board)\n",
    "        if winner is not None:\n",
    "            end = True\n",
    "            \n",
    "        v = []\n",
    "        for i, row in enumerate(board):\n",
    "            for j, x in enumerate(row):\n",
    "                if x == empty:\n",
    "                    v.append((i, j))\n",
    "        if len(v) == 0:\n",
    "            end = True\n",
    "        \n",
    "        index = (index+1)%2\n",
    "        step += 1\n",
    "    return winner, sequence"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "p1 = SDMPlayer('X')\n",
    "p2 = Player('O')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "n = 200\n",
    "wins = defaultdict(int)\n",
    "for i in range(n):\n",
    "    winner, seq = play(p1, p2)\n",
    "    p1.on_finish(winner, seq)\n",
    "    p2.on_finish(winner, seq)\n",
    "    \n",
    "    wins[winner] += 1\n",
    "    \n",
    "    clear_output(wait=True)\n",
    "    print('Game #{:5d}: {}  {}'.format(i+1, list(wins.items()), list(p1.stats.items())))\n",
    "    \n",
    "    #print ''\n",
    "    #print_board(seq[-1])\n",
    "    #print ''\n",
    "    #for board in seq:\n",
    "    #    print_board(board)\n",
    "    #    print ''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "bs1 = p1.board_to_bitstring([[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']])\n",
    "bs2 = p1.board_to_bitstring([['X', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']])\n",
    "bs3 = p1.board_to_bitstring([[' ', 'X', ' '], [' ', ' ', ' '], [' ', ' ', ' ']])\n",
    "bs4 = p1.board_to_bitstring([[' ', ' ', 'X'], [' ', ' ', ' '], [' ', ' ', ' ']])\n",
    "bs5 = p1.board_to_bitstring([[' ', ' ', ' '], ['X', ' ', ' '], [' ', ' ', ' ']])\n",
    "bs6 = p1.board_to_bitstring([[' ', ' ', ' '], [' ', 'X', ' '], [' ', ' ', ' ']])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "#p1.sdm.write(bs1, bs2)\n",
    "#p1.sdm.write(bs1, bs3)\n",
    "#p1.sdm.write(bs1, bs4)\n",
    "p1.sdm.write(bs1, bs5)\n",
    "#p1.sdm.write(bs1, bs6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "rd1 = p1.sdm.read(bs1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[[' ', ' ', ' '], [' ', ' ', ' '], [' ', ' ', ' ']]"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p1.bitstring_to_board(rd1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
